状态模式 State 行为型 设计模式(二十四)
状态模式 State
人有喜怒哀乐,海绵宝宝也会有不同的时候,也会有不同的心情~
问题:上图中,如果跟海绵宝宝开玩笑,那种情况最可能被打?
看下面一个示例,演示了java中的多态特性
类A有方法action()
类B继承了A 覆盖了方法action()
类C继承了A 覆盖了方法action()
package state.example; public class A { void action() { System.out.println("A......."); } public static void main(String[] args) { A a = new A(); A b = new B(); A c = new C(); a.action(); b.action(); c.action(); } } class B extends A { @Override void action() { System.out.println("B......."); } class C extends A { @Override void action() { System.out.println("C......."); } }
类型都是A,但是却因为内部的具体的子类类型不一样,结果不一样
海绵宝宝在不同心情状态下,对同一件事情的处理态度可能是不同的,生气的时候跟他开玩笑很可能会被打。
同样的类型A,由于具体类型状态的不同,给出的响应是不同的。
这就是状态,很多事物都有不同的状态,不同的状态可能会有不同的行为,而且,状态间是可以切换的。
意图
允许一个对象在其内部状态改变时改变他的行为。对象看起来似乎修改了他的类。
别名:状态对象(Objects for States)
其实就是拥有状态属性,在不同的状态下,呈现出不同的行为,并且可以灵活的切换状态。
状态:一个对象的行为取决于内部一个或者多个动态变化的属性,这样的属性就叫做状态。
有状态的对象stateful:这样的对象就叫做有状态的对象。
结构
不考虑Java语言的多态,如何达到“多态”的效果?
一种很自然的解决方案就是条件分支或者选择语句,比如
int state = 0; if(0 == state){ //... }else if(1 == state){ //... }else if(2 == state){ //... }else{ //... }
int类型的变量state 就是“有状态的对象”,state的具体的值就是“状态”。
状态的切换依赖state变量的赋值,不同行为的呈现借助于条件分支。
显然,如果业务逻辑复杂,将会有繁琐复杂的分支判断
更有甚者,如果有多个状态共同决定行为,那么岂不是多个状态的复杂组合了?
而且如果新增加状态,那么就需要修改代码,添加一个新的 else if(),扩展性不好,不符合开闭原则。
我们更希望的是能够达到“多态”的那种调用效果,方法调用与具体的行为解耦。
调用者不需要关注具体的类型,在方法执行时,会具有真实类型的行为。
相当于变形为:
int state = 0; state.action();
看起来,看起来好像,看起来好像action()方法封装了类似下面的判断逻辑(只是看起来,其实他只有他自己状态下的行为,不需要判断)
if(0 == state){ //... }else if(1 == state){ //... }else if(2 == state){ //... }else{ //... }
这不就是上面的多态示例么,如果A表示抽象的状态,B和C表示具体的某种状态
如果所有使用状态的地方,都使用静态类型A,就可以根据实际类型达到多态的效果了。
所以说
状态模式的根本在于借助于OOP的多态机制,描述状态,就可以达到不同行为的效果。
有了不同类型的状态之后,还可以进一步通过一个中间类对他们进行管理,进而实现灵活的状态切换。
所以一种常用的状态模式结果如下
抽象状态角色State
接口或者抽象类,定义状态的抽象含义,并且给出状态的行为接口,这个接口被外界通过环境类调用
可以持有Context的引用,通过Context完成状态切换
具体状态ConcreteState
实现了抽象状态的描述,给出自己的行为,每一个子类型都表示一种具体的状态
环境类角色Context
又叫做上下文,通过环境类Context对状态进行管理
一般做法是将多种状态定义为他的静态属性,环境类中维护一个State,这是当前状态,经常提供切换状态的方法
代码示例
抽象的State,定义状态行为
package state; public interface State { void handle(); }
具体的状态1
package state; public class ConcreateState1 implements State { @Override public void handle() { System.out.println("state 1 do sth"); } }
具体的状态2
package state; public class ConcreateState2 implements State { @Override public void handle() { System.out.println("state 2 do sth"); } }
环境类,内部封装了两个状态
state为当前状态,初始时设置为状态1了
通过changeState方法进行切换
action()方法为封装调用state的handle方法
package state; public class Context { private final State STATE1 = new ConcreateState1(); private final State STATE2 = new ConcreateState2(); private State state = STATE1; public void action() { state.handle(); } public void changeState(int stateValue) { if (1 == stateValue) { state = STATE1; } else if (2 == stateValue) { state = STATE2; } } }
上面的示例代码中,初始状态为状态1 ,action调用状态1,打印 state 1 do sth
状态切换后,调用状态2
状态模式的核心在于引入抽象状态角色State,借助于多态,实现不同的行为,
然后借助于环境类Context实现State的管理,以灵活切换状态。
状态的具体行为,状态切换的时机等等都可以根据实际情况灵活处理,尤其是何时切换状态,谁来负责切换状态。
示例中,在环境类Context中创建了静态的状态对象,你可以根据实际情况动态的创建对象。
如果状态频繁变化,那么事先创建所有状态更合适,如果状态设置后就很少变动,动态创建的形式或许更好。
状态也可以持有环境类的引用,可以获得更多的便利性。
另外的示例
如果仅仅是状态的提取,简化复杂的状态判断逻辑,借助于State角色就可以完成
如果需要状态的切换维护,或者要求状态的顺序等,总之对State的访问需要增加更多的业务逻辑时,那么就可以借助于Context对State进行管理
通过Context封装维护当前状态,也就是Context提供代理方法,代理对当前状态State的直接访问
这样就可以通过Context增加状态切换,顺序限制等更多的处理逻辑
比如下面逻辑,假设状态1,2,3,4,5,通过下面的逻辑,就可以控制状态的切换顺序,当前状态推导出下一个状态
void click(){
currentState.handle();
if(state == 1){
changeState(2);
}else if(state == 2){
changeState(3);
}
//.....
}
完整代码
package state.order; public interface State { void handle(); }
package state.order; public class ConcreateState1 implements State { @Override public void handle() { System.out.println("state 1 do sth"); } }
package state.order; public class ConcreateState2 implements State { @Override public void handle() { System.out.println("state 2 do sth"); } }
package state.order; public class ConcreateState3 implements State { @Override public void handle() { System.out.println("state 3 do sth"); } }
环境Context包括三种状态,初始时当前状态为状态1
每次方法调用,都会按照顺序切换状态
package state.order; public class Context { private final State STATE1 = new ConcreateState1(); private final State STATE2 = new ConcreateState2(); private final State STATE3 = new ConcreateState3(); private State state = STATE1; public void action() { state.handle(); if (state == STATE1) { setState(STATE2); } else if (state == STATE2) { setState(STATE3); } else if (state == STATE3) { setState(STATE1); } } void setState(State state) { this.state = state; } }
从上面的结果可以看得出来,不管你调用多少次方法,他都会完成两个任务,当前状态处理,然后切换状态
所以说,状态的切换时机,场景,不同的方式将会有很多种不同的变化形式。
总结
状态的本质很简单,就是多态的体现,Java是纯粹的面向对象语言,天然的具有多态的特性
所以,在Java中,通过类的层次结构,就可以直接形成一个“状态”体系,顶级父类定义状态的访问接口,具体的实现类定义自身的状态行为。
不过状态模式是多态的更进一步的抽象和概念提升,而不是单纯的利用多态的特性。
状态模式通过引入环境类Context,可以对状态进行管理,提供对状态切换的支持。
通过Context 可以实现对当前状态的“代理”,增加更多的处理逻辑
如果对象的行为依赖他的状态,状态的改变导致行为的变化,而且,代码中含有大量的与对象有关的条件语句的处理逻辑
就可以考虑使用状态模式
状态模式的重点就是将对象的状态切换以及不同的状态具有不同的行为与客户端进行了解耦
不同的状态类型组成了状态的体系结构,Context封装了切换逻辑,可以做到状态的切换对客户端透明
将所有的状态切换行为封装到环境中,而不是分散在业务逻辑方法中,不管是维护性还是扩展性的角度都大大提高。
而且使用起来也会非常灵活,Context也封装代理了当前的状态,可以让客户端对状态一无所知,如同一个代理人
客户端不再需要分情况讨论了,你就告诉Context需要做什么就好了,他自己内部知道状态,也可以切换
状态模式必然会导致大量的状态类出现,如果状态很少,你可能就不会用状态模式了
而且运行时的对象个数也会增加很多
如果增加新的状态类,对于那些涉及到状态切换的代码必然需要修改,不符合开闭原则
但是对于不修改状态的代码,因为通过抽象角色State,面向抽象编程,所以并不需要修改
修改状态的代码是面向具体类型,面向细节了,所以逃不掉了
状态模式,就是多态以及多态的切换
开篇示例中的多态是Java语言层面上的多态,不同类型呈现不同的行为。
状态模式是设计思维业务逻辑上的“多态”,根据状态对象不同的产生不同状态下的行为,不是特指OOP中的多态。
状态模式就是将“不同条件下的行为”进行封装,封装后的对象就是状态对象